import sharp from "sharp";
import fg from "fast-glob";
import fs from "fs-extra";
import path from "path";
import { UnityHelper } from "./UnityHelper.js";
import { PrefabSearcher } from "./PrefabSearcher.js";
import { toolchain } from "../toolchain.js";

declare type CutRegion = {
    left: number,
    right: number,
} | {
    top: number,
    bottom: number
};

export class ImageCutter {
    public async syncFrom(): Promise<void> {
        if (!toolchain.opts.input) {
            console.error('input required!');
            process.exit(1);
        }
        const fromProj = process.env[toolchain.opts.input.toUpperCase()];
        if (!fromProj) {
            console.error('from project not defined');
            process.exit(1);
        }

        const imgRoot = path.join(toolchain.opts.projectRoot, 'Assets/AssetSources/ui/atlas');
        const fromImgRoot = path.join(fromProj, 'Assets/AssetSources/ui/atlas');
        const imgs = await fg(['**/*.png', '**/*.jpg'], { cwd: imgRoot });
        for (const img of imgs) {
            const fromFile = path.join(fromImgRoot, img);
            if (!fs.existsSync(fromFile)) {
                continue;
            }

            const s2 = sharp(fromFile);
            const imgMeta2 = await s2.metadata();
            if (imgMeta2.width == null || imgMeta2.height == null) {
                console.error('Cannot get width/height:', fromFile);
                continue;
            }

            const file = path.join(imgRoot, img);
            const s = sharp(file);
            const imgMeta = await s.metadata();
            if (imgMeta.width == null || imgMeta.height == null) {
                console.error('Cannot get width/height:', file);
                continue;
            }

            if (imgMeta.width < imgMeta2.width || imgMeta.height < imgMeta2.height) {
                continue;
            }

            const meta = await UnityHelper.readTextureMeta(file + '.meta');
            if (meta.spriteBorder.x == 0 && meta.spriteBorder.y == 0 && meta.spriteBorder.z == 0 && meta.spriteBorder.w == 0) {
                continue;
            }

            const meta2 = await UnityHelper.readTextureMeta(fromFile + '.meta');
            if (meta.spriteBorder.x == meta2.spriteBorder.x && meta.spriteBorder.y == meta2.spriteBorder.y && meta.spriteBorder.z == meta2.spriteBorder.z && meta.spriteBorder.w == meta2.spriteBorder.w) {
                continue;
            }

            let newMetaContent = await fs.readFile(file + '.meta', 'utf-8');
            newMetaContent = newMetaContent.replace(/spriteBorder: \{x: \d+, y: \d+, z: \d+, w: \d+\}/, `spriteBorder: {x: ${meta2.spriteBorder.x}, y: ${meta2.spriteBorder.y}, z: ${meta2.spriteBorder.z}, w: ${meta2.spriteBorder.w}}`);
            await fs.writeFile(file + '.meta', newMetaContent, 'utf-8');
            await this.doCut(file, 0, 0, 0, 0);
            break;
        }
    }

    public async cut(): Promise<void> {
        if (!toolchain.opts.input) {
            console.error('input required!');
            process.exit(1);
        }
        const arr = toolchain.opts.input.split(',');
        if (arr.length < 1) {
            console.error('input format error! should be like: D:/xxx.png,left=100,right=100 or D:/xxx.png,top=100,bottom=100');
            process.exit(1);
        }
        let files: string[] = [];
        let file = arr[0];
        const mch = file.match(/\[(\d+):(\d+)\]/);
        if (mch != null) {
            const from = Number(mch[1]);
            const to = Number(mch[2]);
            for (let i = from; i <= to; i++) {
                files.push(file.replace(mch[0], String(i)));
            }
        } else {
            files.push(file);
        }
        let left = 0, right = 0, top = 0, bottom = 0;
        for (let i = 1, len = arr.length; i < len; i++) {
            const [edge, value] = arr[i].split('=');
            if (edge == 'left') left = Number(value);
            if (edge == 'right') right = Number(value);
            if (edge == 'top') top = Number(value);
            if (edge == 'bottom') bottom = Number(value);            
        }
        for (let file of files) {
            if (!path.isAbsolute(file)) file = path.join(toolchain.opts.projectRoot, file);
            if (!fs.existsSync(file)) continue;
            console.log('cut image:', file);
            await this.doCut(file, left, right, top, bottom);
        }
    }

    private async doCut(file: string, left: number, right: number, top: number, bottom: number): Promise<void> {
        if (!left && !right && !top && !bottom) {
            // 如果没有指定裁剪参数，则默认以九宫格border为限，给中间部位预留2像素
            const meta = await UnityHelper.readTextureMeta(file + '.meta');
            let cw = 2, ch = 2;
            if (meta.spriteBorder.x > 0) {
                left = meta.spriteBorder.x + 2;
                cw -= 2;
            }
            if (meta.spriteBorder.z > 0) right = meta.spriteBorder.z + cw;
            if (meta.spriteBorder.y > 0) {
                bottom = meta.spriteBorder.y + 2;
                ch -= 2;
            }
            if (meta.spriteBorder.w > 0) top = meta.spriteBorder.w + ch;
        }
        if (!left && !right && !top && !bottom) {
            console.error('input format error! should be like: D:/xxx.png,left=100,right=100,top=100,bottom=100');
            process.exit(1);
        }
        console.log(`left: ${left}, right: ${right}, top: ${top}, bottom: ${bottom}`);

        if (left > 0 || right > 0) {
            const d = (left + right) % 4;
            if (d != 0) {
                // 不是4的倍数，多留2像素
                if (left < right) {
                    left += 4 - d;
                } else {
                    right += 4 - d;
                }
            }
            await this.cutIt(file, { left, right });
        }
        if (top > 0 || bottom > 0) {
            const d = (top + bottom) % 4;
            if (d != 0) {
                // 不是4的倍数，多留2像素
                if (top < bottom) {
                    top += 4 - d;
                } else {
                    bottom += 4 - d;
                }
            }
            await this.cutIt(file, { top, bottom });
        }

        const prefabSearcher = new PrefabSearcher();
        // 设置所有引用该图片的image type为slice
        const guid = await UnityHelper.readGUID(file + '.meta');
        const potencials = await fg('**/*.prefab', { cwd: toolchain.opts.projectRoot });
        let modifiedCnt = 0;
        for (const p of potencials) {
            const f = path.join(toolchain.opts.projectRoot, p);
            const content = await fs.readFile(f, 'utf-8');
            if (!content.includes(guid)) continue;

            const usages = await prefabSearcher.findImageUsages(f, [guid]);
            // 如果被SpriteRenderer使用，不要切
            const sr = usages.find((v) => v.type == 'SpriteRenderer')
            if (sr) {
                console.error('The texture is used as SpriteRenderer\'s sprite, cut is not allowd. Please roll back.');
                process.exit(1);
            }

            const lines = content.split(/\r?\n/);
            let isHit = false;
            let modified = false;
            for (let i = 0, len = lines.length; i < len; i++) {
                const line = lines[i];
                if (line.startsWith('--- !u!')) {
                    isHit = false;
                    continue;
                }
                if (line.startsWith('  m_Sprite:') && line.includes('guid: ' + guid)) {
                    isHit = true;
                    continue;
                }
                if (isHit && line == '  m_Type: 0') {
                    lines[i] = '  m_Type: 1';
                    modified = true;
                }
            }
            if (modified) {
                modifiedCnt++;
                console.log('M  ', f, `[${usages[0].type}]`, usages[0].path);
                await fs.writeFile(f, lines.join('\n'), 'utf-8');
            } else {
                console.log('-  ', f, `[${usages[0].type}]`, usages[0].path);
            }
        }

        console.log(`${modifiedCnt} related file changed`);
    }

    private async cutIt(file: string, region: CutRegion): Promise<void> {
        const s = sharp(file);
        const meta = await s.metadata();
        if (meta.width == null || meta.height == null) {
            console.error('Cannot get width/height:', file);
            return;
        }

        const tmpSave = 'imgcut.tmp' + path.extname(file);
        if ('left' in region && 'right' in region ) {
            if (region.left > 0 && region.right > 0) {
                const left = await s.extract({ left: 0, top: 0, width: region.left, height: meta.height }).extend({
                    top: 0,
                    bottom: 0,
                    left: 0,
                    right: region.right,
                    background: { r: 0, g: 0, b: 0, alpha: 0 }
                });
                const right = await sharp(file).extract({ left: meta.width - region.right, top: 0, width: region.right, height: meta.height }).extend({
                    top: 0,
                    bottom: 0,
                    left: region.left,
                    right: 0,
                    background: { r: 0, g: 0, b: 0, alpha: 0 }
                });
                await left.composite([
                    {
                        input: await right.toBuffer(),
                        blend: 'xor'
                    }
                ]).toFile(tmpSave);
            } else if (region.left > 0 && region.right == 0) {
                await s.extract({ left: 0, top: 0, width: region.left, height: meta.height }).toFile(tmpSave);
            } else if (region.left == 0 && region.right > 0) {
                await s.extract({ left: meta.width - region.right, top: 0, width: region.right, height: meta.height }).toFile(tmpSave);
            } else {
                return;
            }
        } else if ('top' in region && 'bottom' in region) {
            if (region.top > 0 && region.bottom > 0) {
                const top = await s.extract({ left: 0, top: 0, width: meta.width, height: region.top }).extend({
                    top: 0,
                    bottom: region.bottom,
                    left: 0,
                    right: 0,
                    background: { r: 0, g: 0, b: 0, alpha: 0 }
                });
                const bottom = await sharp(file).extract({ left: 0, top: meta.height - region.bottom, width: meta.width, height: region.bottom }).extend({
                    top: region.top,
                    bottom: 0,
                    left: 0,
                    right: 0,
                    background: { r: 0, g: 0, b: 0, alpha: 0 }
                });
                await top.composite([
                    {
                        input: await bottom.toBuffer(),
                        blend: 'xor'
                    }
                ]).toFile(tmpSave);
            } else if (region.top > 0 && region.bottom == 0) {
                await s.extract({ left: 0, top: 0, width: meta.width, height: region.top }).toFile(tmpSave);
            } else if (region.top == 0 && region.bottom > 0) {
                await s.extract({ left: 0, top: meta.height - region.bottom, width: meta.width, height: region.bottom }).toFile(tmpSave);
            } else {
                return;
            }
        } else {
            return;
        }
        await fs.copyFile(tmpSave, file);
        await fs.unlink(tmpSave);
    }
}
